/*
 * Created on Aug 14, 2003
 *
 * To change the template for this generated file go to
 * Window>Preferences>Java>Code Generation>Code and Comments
 */

/**
 * @author smf9749
 *
 * To change the template for this generated type comment go to
 * Window>Preferences>Java>Code Generation>Code and Comments
 */

import java.io.*;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.cli.*;

public class Du
{
    
    public Du() {}
    public void pathTest() { System.out.println("instance method main"); }
    private static final long KB=1024;
    private static final long MB=KB*1024;
    private static final long GB=MB*1024;
    private static final long TB=GB*1024;
    
    private static void writeTopN(SortedMap<Long, File> map, boolean allowGreek, String header)
    {
        System.out.print(header);
        for ( Map.Entry<Long, File> e: map.entrySet())
        {
            String size;
            long v = ((Long)e.getKey()).longValue();
            
            if ( useGreekPostFix && allowGreek)
                size = justify(7,sizeToString( v ));
            else
                size = justify(11, Long.toString( v ));
            
            File path = (File)e.getValue();
            System.out.println(size + " " + path);
            
        }
    }
    
    private static void writeTops()
    {
        if ( topDirCount > 0 )
            writeTopN(mapTopDirCount, true, "\nTop dirs by dir count directly below it\nSize          Path\n");

        if ( topDirect > 0 )
            writeTopN(mapTopDirect, true, "\nTop dirs by size of files directly below it\nSize          Path\n");
        
        if ( topNumber > 0 )
            writeTopN(mapTopNumber, false, "\nTop dirs by count of files directly below it\nSize          Path\n");
            
        if ( topWhole > 0 )
            writeTopN(mapTopWhole, true, "\nTop dirs by size of files recursively below it\nSize          Path\n");
    }
    
    private static int topDirect = 10;  // track size of file directly below a dir
    private static TreeMap<Long, File> mapTopDirect = new TreeMap<Long, File>();
    
    private static int topNumber = 10;  // tracks counts of files directly below a dir
    private static TreeMap<Long, File> mapTopNumber = new TreeMap<Long, File>();
    
    private static int topWhole  = 10;  // tracks the dirs with the biggest size - kind of a sort of the whole list
    private static TreeMap<Long, File> mapTopWhole = new TreeMap<Long, File>();
    
    private static int topDirCount  = 10;  // tracks the dirs with the biggest size - kind of a sort of the whole list
    private static TreeMap<Long, File> mapTopDirCount = new TreeMap<Long, File>();

    private static TreeSet<File> pathDown = new TreeSet<File>();

    private static String sizeToString(long size)
    {
        if ( size < KB*10 )
        {
            return size + "  ";
        }
        else if ( size < MB*10)
        {
            return size/KB + " K";
        }
        else if ( size < GB*10)
        {
            return size/MB + " M";
        }
        else if ( size < TB*10)
        {
            return size/GB + " G";
        }
        else 
        {
            return size/TB + " T";
        }
    }
    
    private static int depth=3;
    private static boolean stream=false;
    private static boolean fullPath = false;
    private static boolean useGreekPostFix=true;
    private static boolean verbose=false;
    
    private static String justify(int len, String str)
    {
        StringBuffer buffer = new StringBuffer();
        int diff = len - str.length();
        for ( int i=0; i<diff; i++)
            buffer.append(' ');
        buffer.append(str);
        return buffer.toString();
    }
    private static boolean trackTopN(SortedMap<Long, File> map, long value, File dir, long tier)
    {
        if ( tier > 0 )
        {
            if ( map.size() < tier )
            {
                map.put(new Long(value), dir);
                return true;
            }
            else
            {
                Long lowest = map.firstKey();
                int l = lowest.intValue();
                if ( value > l )
                {
                    map.remove(lowest);
                    map.put(new Long(value), dir);
                    return true;
                }
            }
        }
        return false;

    }
    private static long totalCount=0;
    private static long walkDirectory(File currDir, int currdepth) throws IOException
    {
        File cCan = currDir.getCanonicalFile();
        if ( pathDown.contains(cCan) )
        {
            System.err.println("Circular canonical path:" + cCan);
            return 0;
        }
        else
            pathDown.add(cCan);

        try
        {
            File[] children = currDir.listFiles();
            if ( children == null )
            {
                System.out.println("Unable to read children at " + currDir.getAbsolutePath() + " possible permissions problem");
                return 0;
            }
            long countDirect=0;
            long sizeDirect=0;
            long countDirs=0; 
            
            long childrenSize=0;
            long count=0;
            for (int i=0; i<children.length; i++)
            {
                totalCount ++;
                if ( stream && totalCount % 100 == 0)
                    System.out.print(totalCount + "                                                        \r");
                    
                if ( children[i].isDirectory())
                {
                    countDirs++;
                    count++; // count the directory itself
                    childrenSize += children[i].length();
                    childrenSize += walkDirectory(children[i], currdepth+1);
                }
                else
                {
                    // accumulate this file
                    count++; // count the directory itself
                    childrenSize += children[i].length();
                    countDirect++;
                    sizeDirect += children[i].length();
                }
            }
            //??? add stuff to maps here
            if ( topDirect > 0 )
            {
                if ( mapTopDirect.size() < topDirect )
                {
                    mapTopDirect.put(new Long(countDirect), currDir);
                }
                else
                {
                    Long lowest = mapTopDirect.firstKey();
                    int l = lowest.intValue();
                    if ( countDirect > l )
                    {
                        mapTopDirect.remove(lowest);
                        mapTopDirect.put(new Long(countDirect), currDir);
                    }
                }
            }
            
            trackTopN(mapTopDirect, sizeDirect, currDir, topDirect);
            trackTopN(mapTopNumber, countDirect, currDir, topNumber);
            trackTopN(mapTopWhole, childrenSize, currDir, topWhole);
            trackTopN(mapTopDirCount, countDirs, currDir, topDirCount);
            
            if ( stream )
                System.out.print(totalCount + "                                                        \r");
                
            if ( verbose )
            {
                String path=null;
                if ( fullPath )
                    path = currDir.getAbsolutePath();
                else
                    path = currDir.getPath();
                
                String size = null;
                if ( useGreekPostFix )
                    size = justify(7,sizeToString(childrenSize));
                else
                    size = justify(11, Long.toString(childrenSize));
                    
                if ( currdepth <= depth )
                    System.out.println(size + " " + path);
            }           
            return childrenSize;
        }
        catch(IOException ex) {
            for ( int i=0; i<depth; i++ ) {
                System.err.print(' ');
            }
            System.err.println(currDir);
            throw ex;
        }
        finally
        {
            pathDown.remove(cCan);
        }
    }

    public static void main(String[] args)
    {
        Options options = new Options();
        if ( args.length == 0)
        {
            System.err.println("Must provide at least a path to Du from");
            System.exit(1);
        }
        try
        {
            options.addOption("t", true, "display the top N directories based on the size of the files directly contained in it");
            options.addOption("c", true, "display the top N directories based on the number of files directly contained in it");
            options.addOption("w", true, "display the top N directories based on the entrie tree below it");
            options.addOption("i", true, "display the top N directories based on the dir entries directly below it");

            options.addOption("v", false, "verbose mode - write space of each directory");
            options.addOption("h", false, "help");
            
            options.addOption("d", true, "display file usage to only the given depth");
            options.addOption("s", false, "streams the file count as it progresses");
            options.addOption("a", false, "print absolute path");
            options.addOption("g", false, "do NOT use greek postfix (KB, GB, etc)");

            GnuParser parser = new GnuParser();
            CommandLine cmd = parser.parse(options, args);

            String tmp = cmd.getOptionValue("d");
            if (tmp == null)
                depth = Integer.MAX_VALUE;
            else
                depth = Integer.parseInt(tmp);

            tmp = cmd.getOptionValue('t');
            if ( tmp != null )
                topDirect = Integer.parseInt(tmp);

            tmp = cmd.getOptionValue('c');
            if ( tmp != null )
                topNumber = Integer.parseInt(tmp);

            tmp = cmd.getOptionValue('w');
            if ( tmp != null )
                topWhole = Integer.parseInt(tmp);

            tmp = cmd.getOptionValue('i');
            if ( tmp != null )
                topDirCount = Integer.parseInt(tmp);

            if ( cmd.hasOption('s'))
                stream = true;
                
            if ( cmd.hasOption('a') )
                fullPath = true;
                
            if ( cmd.hasOption('g') )
                useGreekPostFix = false;

            if ( cmd.hasOption('h') )
            {
                HelpFormatter formatter = new HelpFormatter();
                formatter.printHelp( "java -jar du.jar [options] path", options );
                System.exit(1);
            }
            File topDir = new File(args[args.length-1]);
            long total=0;
            if ( topDir.isDirectory() )
            {
                total = walkDirectory(topDir, 0);
                if ( !useGreekPostFix )
                    System.out.println("Total space " + total);
                else
                    System.out.println("Total space " + sizeToString(total));
            }
            else
            {
                throw new RuntimeException(topDir + " is not a directory");
            }
            
            writeTops();
        }
//      catch(ParseException pe)
//      {
//          System.err.println("Problem parsing CLI parameters");
//          System.err.println(pe.getMessage());
//          System.err.println("Usage:");
//          Iterator itr = options.getOptions().iterator();
//          while (itr.hasNext())
//              System.err.println(itr.next().toString());
//      }
        catch (Exception e) 
        {
            System.err.println("Exception: " + e);
            e.printStackTrace();

            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp( "java -jar du.jar [options] path", options );
        }

    }
}
